#include "Person.h"

// Object constructor
Person::Person(const String _name, int _prod, int _appet, FTA _algo, int *_speed):
	name(_name), ready(maxReady), id(0), productv(_prod), appetite(_appet), algo(_algo), speed(_speed),
	fork(-1), free(true), thinkTime(1000), eatTime(1000), waitTime(1000)
{
	info.lfork = false;
	info.rfork = false;
	info.fork = -1;
	info.state = Thinking;
	info.hunger = 0;
	running = false;
}

// Copy constructor
Person::Person(const Person &p):
	name(p.name), ready(maxReady), id(p.id), productv(p.productv), appetite(p.appetite), algo(p.algo),
	speed(p.speed), info(p.info), left(p.left), right(p.right), fork(p.fork), free(p.free)
{
	running = false;
}

// Destructor
Person::~Person()
{
	if(running)
		Terminate();
}

// Static variable definition
long long Person::grc = 0;
CriticalSection Person::gcs;

// Calculates delays
void Person::CalcTime()
{
	thinkTime = (productv + rand() % rndFactor) * (1010 - *speed);
	eatTime = (appetite + rand() % rndFactor) * (1010 - *speed);
	waitTime = (maxAppetite - appetite + rand() % rndFactor + 1) * (1010 - *speed) / 4;
}

// Meditation
void Person::Think()
{
	String msg = name + String(" is thinking");
	PostCallback(callback1(Log, msg));
	info.state = Thinking;
	PostCallback(callback2(Update, id, info));
	Thread::Sleep(thinkTime);
}

// Eating
void Person::Eat()
{
	String msg = name + String(" eats spaghetti");
	PostCallback(callback1(Log, msg));
	info.state = Eating;
	info.hunger = 0;
	PostCallback(callback2(Update, id, info));
	Thread::Sleep(eatTime);
}

// Waiting
void Person::Wait()
{
	info.state = Waiting;
	info.hunger++;
	PostCallback(callback2(Update, id, info));
	Thread::Sleep(waitTime);
}

// Getting forks
void Person::TakeForks()
{
	String msg = name + String(" wants to take forks");
	PostCallback(callback1(Log, msg));
	switch(algo)
	{
		case Simple:
			TakeSimple();
			break;
		case Random:
			TakeRandom();
			break;
		case Ordered:
			TakeOrdered();
			break;
		case Voluntary:
			TakeVoluntary();
			break;
	}
}

// An algorithm leading to deadlocks. Left fork first
void Person::TakeSimple()
{
	// Declare the eating intension (Ordered-compatibility)
	gcs.Enter();
	ready = grc++;
	gcs.Leave();
	bool res = false;
	if(!info.lfork) // if we couldn't get a fork (Voluntary-compatibility)
	{
		// Awaiting the left fork
		while(!res)
		{
			cs.Enter();
			if(free || info.lfork) // The fork got freed and has been given to us
				res = true;
			else
			{
				cs.Leave();
				if(!running) return;
				Wait();
			}
		}
		if(!info.lfork)
		{
			// Taking the left fork
			free = false;
			fork = id;
			info.lfork = true;
			info.fork = id;
			// Display
			PostCallback(callback2(Update, id, info));
		}
		cs.Leave();
	}
	if(!info.rfork)
	{
		// Awaiting the right fork
		res = false;
		while(!res)
		{
			right->cs.Enter();
			if(right->free || info.rfork)
				res = true;
			else
			{
				right->cs.Leave();
				if(!running) return;
				Wait();
			}
		}
		if(!info.rfork)
		{
			// Taking the right fork
			right->free = false;
			right->fork = id;
			right->info.fork = id;
			// Display
			info.rfork = true;
			PostCallback(callback2(Update, id, info));
			PostCallback(callback2(UpdateFork, right->id, id));
		}
		right->cs.Leave();
	}
}

// An algorithm leading to deadlocks. The first fork is chosen randomly
void Person::TakeRandom()
{
	// Declare the eating intension (Ordered-compatibility)
	gcs.Enter();
	ready = grc++;
	gcs.Leave();
	// Getting the random order
	bool rnd = rand() % 2;
	Person *p1 = rnd ? this : right;
	Person *p2 = rnd ? right : this;
	volatile bool *ifork1 = rnd ? &(info.lfork) : &(info.rfork);
	volatile bool *ifork2 = rnd ? &(info.rfork) : &(info.lfork);
	bool res = false;
	if(!(*ifork1)) // if we couldn't get a fork (Voluntary-compatibility)
	{
		// Awaiting the first fork
		while(!res)
		{
			p1->cs.Enter();
			if(p1->free || p1->fork == id)
				res = true;
			else
			{
				p1->cs.Leave();
				if(!running) return;
				Wait();
			}
		}
		if(!(*ifork1))
		{
			// Taking the first fork
			p1->free = false;
			p1->fork = id;
			*ifork1 = true;
			p1->info.fork = id;
			// Display
			p2->cs.Enter();
			PostCallback(callback2(Update, p2->id, p2->info));
			p2->cs.Leave();
			PostCallback(callback2(Update, p1->id, p1->info));
		}
		p1->cs.Leave();
	}
	if(!(*ifork2))
	{
		// Awaiting the second fork
		res = false;
		while(!res)
		{
			p2->cs.Enter();
			if(p2->free || p2->fork == id)
				res = true;
			else
			{
				p2->cs.Leave();
				if(!running) return;
				Wait();
			}
		}
		if(!(*ifork2))
		{
			// Taking the second fork
			p2->free = false;
			p2->fork = id;
			*ifork2 = true;
			p2->info.fork = id;
			// Display
			p1->cs.Enter();
			PostCallback(callback2(Update, p1->id, p1->info));
			p1->cs.Leave();
			PostCallback(callback2(Update, p2->id, p2->info));
		}
		p2->cs.Leave();
	}
}

// This algorithm avoids deadlocks but can result into serial sequence
void Person::TakeOrdered()
{
	// Record ourselves in the queue
	gcs.Enter();
	ready = grc++;
	gcs.Leave();
	// Cycle for getting the forks
	bool res = false;
	while(!res)
	{
		// Entering critical section
		cs.Enter();
		if(!info.lfork && (!free || (left->ready < ready)))
		{
			// The fork is busy or it is not our turn. Wait and try again
			cs.Leave();
			if(!running) return;
			Wait();
			continue;
		}
		// Entering critical section for right fork
		right->cs.Enter();
		if(!info.rfork && (!right->free || (right->ready < ready)))
		{
			// The fork is busy or it is not our turn. Wait and try again (with the left fork).
			cs.Leave();
			right->cs.Leave();
			if(!running) return;
			Wait();
			continue;
		}
		// Both forks are free and it is our turn to eat
		if(!info.lfork) // if we couldn't get a fork (Voluntary-compatibility)
		{
			free = false;
			fork = id;
			info.lfork = true;
			info.fork = id;
		}
		if(!info.rfork)
		{
			info.rfork = true;
			right->free = false;
			right->fork = id;
			right->info.fork = id;
		}
		// Update the displayed info
		PostCallback(callback2(Update, id, info));
		PostCallback(callback2(UpdateFork, right->id, right->fork));
		// Done
		cs.Leave();
		right->cs.Leave();
		res = true;
	}
}

// Voluntary fork transfer algorithms. Is the most effective if every philosopher uses it
void Person::TakeVoluntary()
{
	// Record ourselves in the queue (Ordered-compatibility)
	gcs.Enter();
	ready = grc++;
	gcs.Leave();
	// Wait until both forks are free or given to us
	bool res = false;
	while(!res)
	{
		// Entering critical section
		cs.Enter();
		if(!info.lfork && !free)
		{
			// The fork is busy and unavailable. Wait and try again.
			cs.Leave();
			if(!running) return;
			Wait();
			continue;
		}
		// Entering critical section for the right fork
		right->cs.Enter();
		if(!info.rfork && !right->free)
		{
			// The fork is busy or it is not our turn. Wait and try again (with left fork).
			cs.Leave();
			right->cs.Leave();
			if(!running) return;
			Wait();
			continue;
		}
		// Both forks are free and it is our turn to eat
		if(!info.lfork) // если вилку нам не передали
		{
			free = false;
			fork = id;
			info.lfork = true;
			info.fork = id;
		}
		if(!info.rfork) // if we couldn't get a fork
		{
			info.rfork = true;
			right->free = false;
			right->fork = id;
			right->info.fork = id;
		}
		// Update the displayed info
		PostCallback(callback2(Update, id, info));
		PostCallback(callback2(UpdateFork, right->id, right->fork));
		// Done
		cs.Leave();
		right->cs.Leave();
		res = true;
	}
}

// Put the forks back on the table
void Person::PutForks()
{
	// Putting left
	cs.Enter();
	ready = maxReady;
	free = true;
	fork = -1;
	info.lfork = false;
	info.rfork = false;
	info.fork = -1;
	PostCallback(callback2(Update, id, info));
	cs.Leave();
	// Putting right
	right->cs.Enter();
	right->free = true;
	right->fork = -1;
	right->info.fork = -1;
	PostCallback(callback2(UpdateFork, right->id, -1));
	right->cs.Leave();
}

// Voluntary give the forks to neighbors
void Person::SendForks()
{
	// Updating own data fields
	cs.Enter();
	ready = maxReady;
	free = false;
	fork = left->id;
	info.lfork = false;
	info.rfork = false;
	info.fork = fork;
	PostCallback(callback2(Update, id, info));
	cs.Leave();
	// Sending the left fork
	left->cs.Enter();
	left->info.rfork = true;
	PostCallback(callback2(Update, left->id, left->info));
	left->cs.Leave();
	// Sending the right fork
	right->cs.Enter();
	right->free = false;
	right->fork = right->id;
	right->info.lfork = true;
	right->info.fork = right->fork;
	PostCallback(callback2(Update, right->id, right->info));
	right->cs.Leave();	
}

// Worker thread process
void Person::Work()
{
	#ifdef PLATFORM_POSIX
	ret = false;
	#endif
	// Reset params
	cs.Enter();
	fork = -1;
	ready = maxReady;
	free = true;
	info.lfork = false;
	info.rfork = false;
	info.hunger = 0;
	info.state = Thinking;
	cs.Leave();
	// Worker cycle
	while(running)
	{
		CalcTime();
		if(running) Think();
		if(running) TakeForks();
		if(running) Eat();
		if(running) (algo == Voluntary) ? SendForks() : PutForks();
	}
	#ifdef PLATFORM_POSIX
	ret = true;
	#endif
}

// Link with right neighbor
void Person::LinkRight(Person &p)
{
	right = &p;
}

// Link with left neighbor
void Person::LinkLeft(Person &p)
{
	left = &p;
}

// Runs main cycle
void Person::Run()
{
	srand(id);
	running = true;
	thrd.Run(THISBACK(Work));
}

// Stops work
void Person::Stop()
{
	running = false;
}

// Terminates a thread
void Person::Terminate()
{
	if(running) Stop();
	int delay = (productv > appetite) ? (productv + rndFactor)*(1010 - *speed) : (appetite + rndFactor)*(1010 - *speed);
	#ifdef PLATFORM_POSIX // fix for Thread::Wait in U++ 2007.0
	while(!ret) Thread::Sleep(1);
	#endif
	thrd.Wait();
}

// Copy assignment
void Person::operator=(const Person &p)
{
	id = p.id;
	name = p.name;
	productv = p.productv;
	appetite = p.appetite;
	speed = p.speed;
	algo = p.algo;
	info = p.info;
	cs = p.cs;
	fork = p.fork;
	free = p.free;
	ready = maxReady;
	left = p.left;
	right = p.right;
	running = false;
}

// Returns state info
PersonInfo Person::GetInfo()
{
	PersonInfo res;
	cs.Enter();
	res = info;
	cs.Leave();
	return res;
}
